package com.shoulder.authorizationserver.autoconfigure;

import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.shoulder.authorizationserver.enhancer.DefaultJwtTokenEnhancer;
import com.shoulder.authorizationserver.enhancer.JwtTokenEnhancer;
import com.shoulder.authorizationserver.rsa.*;
import com.shoulder.imports.Imports;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.token.*;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

@Imports(value = AutoConfiguration.class)
@AutoConfiguration
public class JwkAutoConfiguration {
    /**
     * 配置 rasKeyPair存储方式,默认配置了一个基于内存的,可以自己自行扩展 redis、jdbc等
     *
     */
    @Bean("inMemoryRsaKeyPairRepository")
    @ConditionalOnMissingBean(RsaKeyPairRepository.class)
    public RsaKeyPairRepository inMemoryRsaKeyPairRepository() {
        return new InMemoryRsaKeyPairRepository();
    }

    @Bean
    @ConditionalOnBean(name = "inMemoryRsaKeyPairRepository")
    public InitRsaKeyPairs initRsaKeyPairs(RsaKeyPairRepository rsaKeyPairRepository
            , @Value("${jwt.key.id}") String id,
                                           @Value("${jwt.key.public}") RSAPublicKey publicKey,
                                           @Value("${jwt.key.private}") RSAPrivateKey privateKey) {
        return new InitRsaKeyPairs(rsaKeyPairRepository, id, publicKey, privateKey);
    }


    /**
     * 配置 jwkSource
     *
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource(RsaKeyPairRepository rsaKeyPairRepository) {
        return new RsaKeyPairJWKSource(rsaKeyPairRepository);
    }

    /**
     * 配置JwtEncoder
     *
     */
    @Bean
    public JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
        return new NimbusJwtEncoder(jwkSource);
    }

    /**
     * 配置jwt解析器
     * 在资源服务器中配置了 此处加上 primary 标识
     * com.shoulder.resourceserver.autoconfigure.ResourceSecurityAutoConfiguration#jwtDecoder(com.shoulder.core.properties.SecurityProperties)
     *
     * @param jwkSource jwk源
     * @return JwtDecoder
     */
    @Primary
    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    /**
     * 定义一个默认的 jwtToken增强器（@ConditionalOnMissingBean）
     *
     */
    @ConditionalOnMissingBean(JwtTokenEnhancer.class)
    @Bean
    public JwtTokenEnhancer jwtTokenEnhancer() {
        return new DefaultJwtTokenEnhancer();
    }


    /**
     * 配置 OAuth2Token 的生成器
     * <p>
     * <a href="https://docs.spring.io/spring-authorization-server/reference/core-model-components.html#oauth2-token-generator">...</a>
     * <p>
     * OAuth2TokenContext 是一个持有与 OAuth2Token 相关信息的 context 对象，并由 OAuth2TokenGenerator 和 OAuth2TokenCustomizer 使用
     * OAuth2TokenGenerator 负责从所提供的 OAuth2TokenContext 中的信息生成 OAuth2Token。生成的 OAuth2Token 主要取决于在 OAuth2TokenContext 中指定的 OAuth2TokenType
     * <p>
     * 此外，生成的 OAuth2AccessToken 的格式是不同的，取决于为 RegisteredClient 配置的 TokenSettings.getAccessTokenFormat()。
     * 如果格式是 OAuth2TokenFormat.SELF_CONTAINED（默认），那么就会生成一个 Jwt。如果格式是 OAuth2TokenFormat.REFERENCE，那么就会生成一个 "opaque" token
     */
    @Bean
    public OAuth2TokenGenerator<OAuth2Token> oAuth2TokenGenerator(JwtEncoder jwtEncoder, OAuth2TokenCustomizer<JwtEncodingContext> customizer) {
        JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
        jwtGenerator.setJwtCustomizer(customizer);
        OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
        OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
        return new DelegatingOAuth2TokenGenerator(jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
    }

    /**
     * 配置 OAuth2TokenCustomizer，扩展jwt
     * <p>
     * <a href="https://docs.spring.io/spring-authorization-server/reference/core-model-components.html#oauth2-token-customizer">...</a>
     * <p>
     * OAuth2TokenCustomizer 提供了定制 OAuth2Token 属性的能力，这些属性可以在提供的 OAuth2TokenContext 中访问。它被 OAuth2TokenGenerator 使用，让它在生成 OAuth2Token 之前自定义其属性
     * <p>
     * 这里的泛型有两个： OAuth2TokenClaimsContext 和  JwtEncodingContext，前者提供了定制 "opaque" OAuth2AccessToken 的能力，后者提供了定制 Jwt 的header和claim的能力
     */
    @Bean
    public OAuth2TokenCustomizer<JwtEncodingContext> oAuth2TokenCustomizer(JwtTokenEnhancer jwtTokenEnhancer) {
        return new RsaKeyPairJWKTokenCustomizer(jwtTokenEnhancer);
    }

    /**
     * 配置 jwtAuthentication对象的转换规则
     */
    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        grantedAuthoritiesConverter.setAuthorityPrefix("");
        grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }

}
